// glmath.js
// Author: tulrich@google.com (Thatcher Ulrich),
//         partly derived from Dean McNamee's Soft3d.js
//
// Some math helpers for use in WebGL programs.
//
// matrix & vector math & V8 tricks stolen from Dean McNamee's Soft3d.js
//
// WARNING: I reordered/renumbered his matrix elements to match OpenGL
// conventions.
//
// Also, if it helps understand the code: in my mind, vectors are
// column vectors, and a transform is "Mx" where M is the matrix and x
// is the vector.
 
glmath = (function() {
 
    // ------------------------------------------------------------
    function newVec(opt_x, opt_y, opt_z) {
      return {x: opt_x ? opt_x : 0, y: opt_y ? opt_y : 0, z: opt_z ? opt_z : 0};
    }
 
    function vectorDupe(a) {
      return {x: a.x, y: a.y, z: a.z};
    }
 
    function vectorCopyTo(a, b) {
      b.x = a.x;
      b.y = a.y;
      b.z = a.z;
    }
 
    function crossProduct(a, b) {
      // a1b2 - a2b1, a2b0 - a0b2, a0b1 - a1b0
      return {
        x: a.y * b.z - a.z * b.y,
        y: a.z * b.x - a.x * b.z,
        z: a.x * b.y - a.y * b.x
      };
    }
 
    function dotProduct(a, b) {
      return a.x * b.x + a.y * b.y + a.z * b.z;
    }
 
    function vectorAdd(a, b) {
      return {x: a.x + b.x, y: a.y + b.y, z: a.z + b.z};
    }
 
    function vectorSub(a, b) {
      return {x: a.x - b.x, y: a.y - b.y, z: a.z - b.z};
    }
 
    function vectorScale(v, s) {
      return {x: v.x * s, y: v.y * s, z: v.z * s};
    }
 
    function vectorNormalize(v) {
      var l2 = dotProduct(v, v);
      if (l2 <= 0) {
        // Punt.
        return {x:1, y:0, z:0};
      }
      var scale = 1 / Math.sqrt(l2);
      return {x: v.x * scale, y: v.y * scale, z: v.z * scale};
    }
 
    function vectorLengthSquared(v) {
      return dotProduct(v, v);
    }
 
    function vectorLength(v) {
      var l2 = dotProduct(v, v);
      return Math.sqrt(l2);
    }
 
    function vectorDistance(a, b) {
      var dx = (a.x - b.x);
      var dy = (a.y - b.y);
      var dz = (a.z - b.z);
      var l2 =  dx * dx + dy * dy + dz * dz;
      return Math.sqrt(l2);
    }
 
    // ------------------------------------------------------------
 
    // NOTE(tulrich): the element numbering is based on OpenGL/DX
    // conventions, where memory is laid out like:
    //
    // [ x[0]   x[4]   x[8]   x[12] ]
    // [ x[1]   x[5]   x[9]   x[13] ]
    // [ x[2]   x[6]   x[10]  x[14] ]
    // [ x[3]   x[7]   x[11]  x[15] ]
    //
    // The translate part is in x[12], x[13], x[14].  We don't
    // actually store the bottom row, so elements 3, 7, 11, and 15
    // don't exist!
 
    // This represents an affine 3x4 matrix.  This was originally just done with
    // object literals, but there is a 10 property limit for map sharing in v8.
    // Since we have 12 properties, and don't generally construct matrices in
    // critical loops, using a constructor function makes sure the map is shared.
    function AffineMatrix(e0, e4, e8, e12, e1, e5, e9, e13, e2, e6, e10, e14) {
      this.e0  = e0;
      this.e4  = e4;
      this.e8  = e8;
      this.e12 = e12;
      this.e1  = e1;
      this.e5  = e5;
      this.e9  = e9;
      this.e13 = e13;
      this.e2  = e2;
      this.e6  = e6;
      this.e10 = e10;
      this.e14  = e14;
    };
 
    function matrixToFloatArray(m) {
      return new WebGLFloatArray([
          m.e0, m.e1, m.e2, 0,
          m.e4, m.e5, m.e6, 0,
          m.e8, m.e9, m.e10, 0,
          m.e12, m.e13, m.e14, 1]);
    }
 
    // Return a vector of the specified column.
    function matrixColumn(mat, col) {
      if (col == 0) {
        return newVec(mat.e0, mat.e1, mat.e2);
      } else if (col == 1) {
        return newVec(mat.e4, mat.e5, mat.e6);
      } else if (col == 2) {
        return newVec(mat.e8, mat.e9, mat.e10);
      } else {
        return newVec(mat.e12, mat.e13, mat.e14);
      }
    };
 
    // Return a vector of the specified column.
    function setMatrixColumn(mat, col, vec) {
      if (col == 0) {
        mat.e0 = vec.x;
        mat.e1 = vec.y;
        mat.e2 = vec.z;
      } else if (col == 1) {
        mat.e4 = vec.x;
        mat.e5 = vec.y;
        mat.e6 = vec.z;
      } else if (col == 2) {
        mat.e8 = vec.x;
        mat.e9 = vec.y;
        mat.e10 = vec.z;
      } else {
        mat.e12 = vec.x;
        mat.e13 = vec.y;
        mat.e14 = vec.z;
      }
    };
 
    // Take one of our matrices and copy it into the given Gl matrix,
    // which should be a CanvasFloatArray with 16 elements.
    function affineMatrixToGlMatrix(m, glm) {
      glm[0] = m.e0;
      glm[1] = m.e1;
      glm[2] = m.e2;
      glm[3] = 0;
      glm[4] = m.e4;
      glm[5] = m.e5;
      glm[6] = m.e6;
      glm[7] = 0;
      glm[8] = m.e8;
      glm[9] = m.e9;
      glm[10] = m.e10;
      glm[11] = 0;
      glm[12] = m.e12;
      glm[13] = m.e13;
      glm[14] = m.e14;
      glm[15] = 1;
    };
 
    function setAffineMatrix(out, e0, e4, e8, e12, e1, e5, e9, e13, e2, e6, e10, e14) {
      out.e0  = e0;
      out.e4  = e4;
      out.e8  = e8;
      out.e12 = e12;
      out.e1  = e1;
      out.e5  = e5;
      out.e9  = e9;
      out.e13 = e13;
      out.e2  = e2;
      out.e6  = e6;
      out.e10 = e10;
      out.e14  = e14;
    }
 
    function copyAffineMatrix(dest, src) {
      dest.e0  = src.e0;
      dest.e4  = src.e4;
      dest.e8  = src.e8;
      dest.e12 = src.e12;
      dest.e1  = src.e1;
      dest.e5  = src.e5;
      dest.e9  = src.e9;
      dest.e13 = src.e13;
      dest.e2  = src.e2;
      dest.e6  = src.e6;
      dest.e10 = src.e10;
      dest.e14 = src.e14;
    }
 
    // Apply the affine 3x4 matrix transform to point |p|.  |p| should
    // be a 3 element array, and |t| should be a 16 element array...
    // Technically transformations should be a 4x4 matrix for
    // homogeneous coordinates, but we're not currently using the
    // extra abilities so we can keep things cheaper by avoiding the
    // extra row of calculations.
    function transformPoint(t, p) {
      return {
          x: t.e0 * p.x + t.e4 * p.y + t.e8  * p.z + t.e12,
          y: t.e1 * p.x + t.e5 * p.y + t.e9  * p.z + t.e13,
          z: t.e2 * p.x + t.e6 * p.y + t.e10 * p.z + t.e14
          };
    }
    // As above, but puts result in given output object.
    function transformPointTo(t, p, out) {
      out.x = t.e0 * p.x + t.e4 * p.y + t.e8  * p.z + t.e12;
      out.y = t.e1 * p.x + t.e5 * p.y + t.e9  * p.z + t.e13;
      out.z = t.e2 * p.x + t.e6 * p.y + t.e10 * p.z + t.e14;
    }
 
    function applyRotation(t, p) {
      return {
          x: t.e0 * p.x + t.e4 * p.y + t.e8  * p.z,
          y: t.e1 * p.x + t.e5 * p.y + t.e9  * p.z,
          z: t.e2 * p.x + t.e6 * p.y + t.e10 * p.z
      };
    }
 
    function applyInverseRotation(t, p) {
      return {
          x: t.e0 * p.x + t.e1 * p.y + t.e2  * p.z,
          y: t.e4 * p.x + t.e5 * p.y + t.e6  * p.z,
          z: t.e8 * p.x + t.e9 * p.y + t.e10 * p.z
      };
    }
 
    // This is an unrolled matrix multiplication of a x b.  It is really a 4x4
    // multiplication, but with 3x4 matrix inputs and a 3x4 matrix output.  The
    // last row is implied to be [0, 0, 0, 1].
    function multiplyAffine(a, b) {
      // Avoid repeated property lookups by making access into the local frame.
      var a0 = a.e0, a1 = a.e1, a2 = a.e2, a4 = a.e4, a5 = a.e5, a6 = a.e6;
      var a8 = a.e8, a9 = a.e9, a10 = a.e10, a12 = a.e12, a13 = a.e13, a14 = a.e14;
      var b0 = b.e0, b1 = b.e1, b2 = b.e2, b4 = b.e4, b5 = b.e5, b6 = b.e6;
      var b8 = b.e8, b9 = b.e9, b10 = b.e10, b12 = b.e12, b13 = b.e13, b14 = b.e14;
 
      return new AffineMatrix(
          a0 * b0  + a4 * b1  + a8 * b2,
          a0 * b4  + a4 * b5  + a8 * b6,
          a0 * b8  + a4 * b9  + a8 * b10,
          a0 * b12 + a4 * b13 + a8 * b14 + a12,
 
          a1 * b0  + a5 * b1  + a9 * b2,
          a1 * b4  + a5 * b5  + a9 * b6,
          a1 * b8  + a5 * b9  + a9 * b10,
          a1 * b12 + a5 * b13 + a9 * b14 + a13,
 
          a2 * b0  + a6 * b1  + a10 * b2,
          a2 * b4  + a6 * b5  + a10 * b6,
          a2 * b8  + a6 * b9  + a10 * b10,
          a2 * b12 + a6 * b13 + a10 * b14 + a14
                              );
    }
    // As above, but writing results to the given output matrix.
    function multiplyAffineTo(a, b, out) {
      // Avoid repeated property lookups by making access into the local frame.
      var a0 = a.e0, a1 = a.e1, a2 = a.e2, a4 = a.e4, a5 = a.e5, a6 = a.e6;
      var a8 = a.e8, a9 = a.e9, a10 = a.e10, a12 = a.e12, a13 = a.e13, a14 = a.e14;
      var b0 = b.e0, b1 = b.e1, b2 = b.e2, b4 = b.e4, b5 = b.e5, b6 = b.e6;
      var b8 = b.e8, b9 = b.e9, b10 = b.e10, b12 = b.e12, b13 = b.e13, b14 = b.e14;
 
      out.e0 = a0 * b0  + a4 * b1  + a8 * b2;
      out.e4 = a0 * b4  + a4 * b5  + a8 * b6;
      out.e8 = a0 * b8  + a4 * b9  + a8 * b10;
      out.e12 = a0 * b12 + a4 * b13 + a8 * b14 + a12;
      out.e1 = a1 * b0  + a5 * b1  + a9 * b2;
      out.e5 = a1 * b4  + a5 * b5  + a9 * b6;
      out.e9 = a1 * b8  + a5 * b9  + a9 * b10;
      out.e13 = a1 * b12 + a5 * b13 + a9 * b14 + a13;
      out.e2 = a2 * b0  + a6 * b1  + a10 * b2;
      out.e6 = a2 * b4  + a6 * b5  + a10 * b6;
      out.e10 = a2 * b8  + a6 * b9  + a10 * b10;
      out.e14 = a2 * b12 + a6 * b13 + a10 * b14 + a14;
    }
 
    function makeIdentityAffine() {
      return new AffineMatrix(
          1, 0, 0, 0,
          0, 1, 0, 0,
          0, 0, 1, 0
                              );
    }
 
    function makeRotateAxisAngle(axis, angle) {
      var c = Math.cos(angle); var s = Math.sin(angle); var C = 1-c;
      var xs = axis.x * s;   var ys = axis.y * s;   var zs = axis.z * s;
      var xC = axis.x * C;   var yC = axis.y * C;   var zC = axis.z * C;
      var xyC = axis.x * yC; var yzC = axis.y * zC; var zxC = axis.z * xC;
      return new AffineMatrix(
          axis.x * xC + c,        xyC - zs,          zxC + ys, 0,
          xyC + zs,        axis.y * yC + c,          yzC - xs, 0,
          zxC - ys,               yzC + xs,   axis.z * zC + c, 0);
    }
 
    // http://en.wikipedia.org/wiki/Rotation_matrix
    function makeRotateAffineX(theta) {
      var s = Math.sin(theta);
      var c = Math.cos(theta);
      return new AffineMatrix(
        1, 0,  0, 0,
        0, c, -s, 0,
        0, s,  c, 0
      );
    }
    function makeRotateAffineXTo(theta, out) {
      var s = Math.sin(theta);
      var c = Math.cos(theta);
      setAffineMatrix(out,
        1, 0,  0, 0,
        0, c, -s, 0,
        0, s,  c, 0
      );
    }
 
    function makeRotateAffineY(theta) {
      var s = Math.sin(theta);
      var c = Math.cos(theta);
      return new AffineMatrix(
         c, 0, s, 0,
         0, 1, 0, 0,
        -s, 0, c, 0
      );
    }
    function makeRotateAffineYTo(theta, out) {
      var s = Math.sin(theta);
      var c = Math.cos(theta);
      setAffineMatrix(out,
         c, 0, s, 0,
         0, 1, 0, 0,
        -s, 0, c, 0);
    }
 
    function makeRotateAffineZ(theta) {
      var s = Math.sin(theta);
      var c = Math.cos(theta);
      return new AffineMatrix(
        c, -s, 0, 0,
        s,  c, 0, 0,
        0,  0, 1, 0
      );
    }
    function makeRotateAffineZTo(theta, out) {
      var s = Math.sin(theta);
      var c = Math.cos(theta);
      setAffineMatrix(out,
                      c, -s, 0, 0,
                      s,  c, 0, 0,
                      0,  0, 1, 0);
    }
 
    function makeTranslateAffine(dx, dy, dz) {
      return new AffineMatrix(
        1, 0, 0, dx,
        0, 1, 0, dy,
        0, 0, 1, dz
      );
    }
 
    function makeScaleAffine(sx, sy, sz) {
      return new AffineMatrix(
        sx,  0,  0, 0,
         0, sy,  0, 0,
         0,  0, sz, 0
      );
    }
 
//     // Return the transpose of the inverse done via the classical adjoint.
//     // This skips division by the determinant, so transformations by the
//     // resulting transform will have to be renormalized.
//     function transAdjoint(a) {
//       var a0 = a.e0, a1 = a.e1, a2 = a.e2, a4 = a.e4, a5 = a.e5;
//       var a6 = a.e6, a8 = a.e8, a9 = a.e9, a10 = a.e10;
//       return new AffineMatrix(
//         a10 * a5 - a6 * a9,
//         a6 * a8 - a4 * a10,
//         a4 * a9 - a8 * a5,
//         0,
//         a2 * a9 - a10 * a1,
//         a10 * a0 - a2 * a8,
//         a8 * a1 - a0 * a9,
//         0,
//         a6 * a1 - a2 * a5,
//         a4 * a2 - a6 * a0,
//         a0 * a5 - a4 * a1,
//         0
//       );
//     }
 
    // Return the inverse of the rotation part of matrix a, assuming
    // that a is normalized.  This is just the transpose of the 3x3
    // rotation part.
    function invertNormalizedRotation(a) {
      return new AffineMatrix(
          a.e0, a.e1, a.e2, 0,
          a.e4, a.e5, a.e6, 0,
          a.e8, a.e9, a.e10, 0);
    }
 
    // Return the inverse of the given affine matrix, assuming that
    // the rotation part is normalized, by exploiting
    // transpose==inverse for rotation matrix.
    function invertNormalized(a) {
      var m = invertNormalizedRotation(a);
      var trans_prime = transformPoint(m, {x:a.e12, y:a.e13, z:a.e14});
      m.e12 = -trans_prime.x;
      m.e13 = -trans_prime.y;
      m.e14 = -trans_prime.z;
      return m;
    }
 
    // Maps 0,0,0 to pos, maps x-axis to dir, maps y-axis to
    // up.  maps z-axis to the right.
    function makeOrientationAffine(pos, dir, up) {
      var right = crossProduct(dir, up);
      return new AffineMatrix(
          dir.x, up.x, right.x, pos.x,
          dir.y, up.y, right.y, pos.y,
          dir.z, up.z, right.z, pos.z
                              );
    }
 
    // Maps object dir (i.e. x) to -z, object right (i.e. z) to x,
    // object up (i.e. y) to y, object pos to (0,0,0).
    //
    // I.e. conventional OpenGL "Eye" coordinates.
    //
    // You would normally use this like:
    //
    //   var object_mat = jsgl.makeOrientationAffine(obj_pos, obj_dir, obj_up);
    //   var camera_mat = jsgl.makeOrientationAffine(cam_pos, cam_dir, cam_up);
    //   var view_mat = jsgl.makeViewFromOrientation(camera_mat);
    //   var proj_mat = jsgl.makeWindowProjection(win_width, win_height, fov);
    //
    //   // To draw object:
    //   context.setTransform(jsgl.multiplyAffine(proj_mat,
    //       jsgl.multiplyAffine(view_mat, object_mat));
    //   context.drawTris(verts, trilist);
    function makeViewFromOrientation(orient) {
      // Swap x & z axes, negate z axis (even number of swaps
      // maintains right-handedness).
      var m = new AffineMatrix(
          orient.e8,  orient.e4, -orient.e0, orient.e12,
          orient.e9,  orient.e5, -orient.e1, orient.e13,
          orient.e10, orient.e6, -orient.e2, orient.e14);
      return invertNormalized(m);
    }
 
    // Maps "Eye" coordinates into (preprojection) window coordinates.
    // Window coords:
    //    wz > 0  --> in front of eye, not clipped
    //    wz <= 0 --> behind eye; clipped
    //    [wx/wz,wy/wz] == [0,0] (at upper-left)
    //    [wx/wz,wy/wz] == [win_width,win_height] (at lower-right)
    //
    // (This is simplified, and different from OpenGL.)
    function makeWindowProjection(win_width, win_height, fov_x_radians) {
      var half_width = win_width / 2;
      var half_height = win_height / 2;
      var tan_half = Math.tan(fov_x_radians / 2);
      var scale = half_width / tan_half;
      return new AffineMatrix(
          scale, -0, -scale, 0,
          0, -scale, -half_height / tan_half, 0,
          0,      0, -1, 0);
    }
 
    function normalizeMatrixRotation(m) {
      var dir = matrixColumn(m, 0);
      var up = matrixColumn(m, 1);
      var right = vectorNormalize(crossProduct(dir, up));
      up = vectorNormalize(crossProduct(right, dir));
      dir = crossProduct(up, right);
      setMatrixColumn(m, 0, dir);
      setMatrixColumn(m, 1, up);
      setMatrixColumn(m, 2, right);
    }
 
    function projectPoint(p) {
      if (p.z <= 0) {
        return {x: 0, y: 0, z: p.z};
      } else {
        return {x: p.x / p.z, y: p.y / p.z, z: p.z};
      }
    }
    function projectPointTo(p, out) {
      out.z = p.z;
      if (p.z <= 0) {
        out.x = 0;
        out.y = 0;
      } else {
        out.x = p.x / p.z;
        out.y = p.y / p.z;
      }
    }
 
    return {
        newVec: newVec,
        vectorDupe: vectorDupe,
        vectorCopyTo: vectorCopyTo,
        crossProduct: crossProduct,
        dotProduct: dotProduct,
        vectorAdd: vectorAdd,
        vectorSub: vectorSub,
        vectorScale: vectorScale,
        vectorNormalize: vectorNormalize,
        vectorLength: vectorLength,
        vectorLengthSquared: vectorLengthSquared,
        vectorDistance: vectorDistance,
        AffineMatrix: AffineMatrix,
        matrixToFloatArray: matrixToFloatArray,
        matrixColumn: matrixColumn,
        setMatrixColumn: setMatrixColumn,
        copyAffineMatrix: copyAffineMatrix,
        transformPoint: transformPoint,
        transformPointTo: transformPointTo,
        applyRotation: applyRotation,
        applyInverseRotation: applyInverseRotation,
        multiplyAffine: multiplyAffine,
        multiplyAffineTo: multiplyAffineTo,
        makeIdentityAffine: makeIdentityAffine,
        makeRotateAxisAngle: makeRotateAxisAngle,
        makeRotateAffineX: makeRotateAffineX,
        makeRotateAffineXTo: makeRotateAffineXTo,
        makeRotateAffineY: makeRotateAffineY,
        makeRotateAffineYTo: makeRotateAffineYTo,
        makeRotateAffineZ: makeRotateAffineZ,
        makeRotateAffineZTo: makeRotateAffineZTo,
        makeTranslateAffine: makeTranslateAffine,
        makeScaleAffine: makeScaleAffine,
        invertNormalizedRotation: invertNormalizedRotation,
        invertNormalized: invertNormalized,
        makeOrientationAffine: makeOrientationAffine,
        makeViewFromOrientation: makeViewFromOrientation,
        makeWindowProjection: makeWindowProjection,
        normalizeMatrixRotation: normalizeMatrixRotation,
        projectPoint: projectPoint,
        projectPointTo: projectPointTo,
 
        affineMatrixToGlMatrix: affineMatrixToGlMatrix,
    };
})();
